## Du test à la preuve :
### Introduction au Property Based Testing
Une personne de plus de 18 ans est un adulte
Une personne émancipé est un adulte
Un adulte peut obtenir le permis
```php
final class Person {
public function emancipate(): void;
public function isAdult(Clock $clock): bool;
public function age(Clock $clock): int;
public function obtainDriverLicense(
Clock $clock,
DeliverDriverLicense $deliver
): void;
public function hasDriverLicense(): bool;
}
```
```php
final class DeliverDriverLicense {
public function __invoke(
string $firstName,
string $lastName,
PointInTime $birthday,
string $placeOfBirth
): DriverLicense;
}
```
Note:
On est dans un projet réel
Un collègue l'a déjà implémenté donc on n'a même pas à s'en préoccuper
Tests traditionnels
public function testIsAdult()
{
$clock = new FrozenClock(new PointInTime('2020-05-15 11:30:00'));
$person = new Person(
'John',
'Doe',
$clock->at('2000-05-16 03:00:00'),
'Somewhere',
);
$this->assertTrue($person->isAdult($clock));
}
public function testIsNotAndAdult()
{
$clock = new FrozenClock(new PointInTime('2020-05-15 11:30:00'));
$person = new Person(
'John',
'Doe',
$clock->at('2004-05-14 03:00:00'),
'Somewhere',
);
$this->assertFalse($person->isAdult($clock));
}
```php
public function testAgeBeforeBirthday();
public function testAgeAfterBirthday();
public function testEmancipatedCitizenIsConsideredAnAdult();
public function testAnAdultCanObtainADriverLicense();
public function testANonAdultCantObtainADriverLicense();
```
![](pictures/traditional.png)
Note:
3 règles couvertes
100% coverage
C'est bon ça fonctionne...
...pour John Doe
Propery Based Testing
### Un peu d'historique
Note:
Vient de la prog fonctionnelle
Née en 1999 avec QuickCheck (Haskell) par John Hughes
### Les principes
Note:
Type system
On décrit un comportement attendu
Le framework va chercher un moyen d'invalider ce comportement
use Innmind\BlackBox\{
PHPUnit\BlackBox,
Set,
};
class Test extends \PHPUnit\Framework\TestCase
{
use BlackBox;
public function testProperty()
{
$this->forAll(/* inputs */)
->then(function(...$inputs) {
// assertions
});
}
}
public function testCitizenIsConsideredAnAdultWhenHeReachesHis18thBirthday()
{
$this
->forAll(
PointInTime::before('3000-01-01T00:00:00'),
$this->ageBetween(18, 130), // small chance someone will be older than that
Set\Strings::any(),
Set\Strings::any(),
Set\Strings::any(),
)
->then(function($birthday, $age, $firstName, $lastName, $placeOfBirth) {
$now = $birthday->goForward($age);
$clock = new FrozenClock($now);
$person = new Person(
$firstName,
$lastName,
$birthday,
$placeOfBirth,
);
$this->assertTrue($person->isAdult($clock));
});
}
# PersonSet.php
public static function over18YearsOld(): Set
{
return Set\Composite::mutable(
function($firstname, $lastname, $birthday, $age, $placeOfBirth) {
return [
new Person($firstname, $lastname, $birthday, $placeOfBirth),
new FrozenClock($birthday->goForward($age)),
];
},
Set\Strings::any(),
Set\Strings::any(),
PointInTime::before('3000-01-01T00:00:00'),
self::ageAbove(18),
Set\Strings::any(),
);
}
# PersonSet.php
public static function anyAdult(): Set
{
return new Set\Either(
self::over18YearsOld(),
self::emancipated(),
);
}
public function testAnyAdultCanObtainADriverLicense()
{
$this
->forAll(PersonSet::anyAdult())
->then(function($personAndClock) {
[$person, $clock] = $personAndClock;
$deliver = new DeliverDriverLicense($clock);
$this->assertFalse($person->hasDriverLicense());
$person->obtainDriverLicense($clock, $deliver);
$this->assertTrue($person->hasDriverLicense());
});
}
![](pictures/pbt.png)
## En résumé
Note:
Des tests vraiment exhaustifs
Les tests deviennent une documentation vivante
Renforce la méfiance sur les tests verts
# Questions
Twitter @Baptouuuu
Github @Baptouuuu/talks